// sdlayout.hpp
//

#include "base.hpp"



class SDLayout;
class SDType;
class SDUniformBlock;



// pad_size
//
inline constexpr size_t pad_size ( size_t size,
                                   size_t alignment )
{
  if (alignment <= 1)
    return size;
  else
    return (size + alignment - 1) & ~(alignment - 1);
}



// pad_array_size
//
// [FIXME] not sure if the last element should be padded too?
//
inline constexpr size_t pad_array_size ( size_t elem_size,
                                         size_t elem_alignment,
                                         size_t array_size )
{
  assert(array_size >= 1);
  return pad_size(elem_size, elem_alignment) * (array_size - 1) + elem_size;
}



// SDBuiltinTypeInfo
//
struct SDBuiltinTypeInfo
{
  std::string name;
  GLenum gl_type;
  size_t size;
  size_t alignment;
};



// SDStructMemberInfo
//
struct SDStructMemberInfo
{
  std::string name;
  std::string type;
  size_t array_size{1};
};



// SDStructTypeInfo
//
struct SDStructTypeInfo
{
  std::string name;
  std::vector<SDStructMemberInfo> members;
};



// SDUniformBlockInfo
//
struct SDUniformBlockInfo
{
  std::string name;
  std::vector<SDStructMemberInfo> members;
};



// SDTypeMember
//
class SDTypeMember
{
  friend SDLayout;
  
public:
  SDTypeMember ( std::string name,
                 const SDType &type,
                 size_t offset,
                 size_t array_size )
    : name(name),
      type(type),
      offset(offset),
      array_size(array_size)
  {
    GLT_ASSERT(array_size >= 1);
  }

  const std::string &get_name () const { return name; }
  const SDType &get_type () const { return type; }
  
private:
  std::string name;
  const SDType &type;
  size_t offset;
  size_t array_size;
};



// SDType
//
struct SDType
{
  std::string name;
  GLenum gl_type;
  size_t size{};
  size_t alignment{};
  std::vector<SDTypeMember> members{};
};



// SDUniformComponent
//
struct SDUniformComponent
{
  const SDUniformBlock &block;
  std::string full_name;
  const SDType &type;
  size_t offset;
  size_t size;
};



// SDUniformBlock
//
class SDUniformBlock
{
  friend SDLayout;
  
public:
  SDUniformBlock ( std::string name,
                   const SDType &type )
    : self(name, type, 0, 1)
  {
  }

  const std::string &get_name () const { return self.get_name(); }
  const SDType &get_type () const { return self.get_type(); }

private:
  SDTypeMember self;
  std::vector<SDUniformComponent *> components;
};



// SDLayout
//
class SDLayout
{
public:
  SDLayout ();
  
  const SDType &register_builtin_type ( SDBuiltinTypeInfo info );
  const SDType &register_struct_type ( SDStructTypeInfo info );
  void register_uniform_block ( SDUniformBlockInfo info );

  const SDType &get_type ( const std::string &name );
  const SDUniformBlock &get_uniform_block ( const std::string &name );
  const SDUniformComponent *lookup_uniform_component ( const std::string &name );
  
  // debug
  void dump_uniform_block ( const std::string &name );
  
private:
  std::unordered_map<std::string, SDType> types;
  std::unordered_map<std::string, SDUniformBlock> uniform_blocks;
  std::unordered_map<std::string, SDUniformComponent> uniform_components;

  SDType &_get_type ( const std::string &name );

  SDType &register_type ( std::string name,
                          GLenum gl_type,
                          size_t size,
                          size_t alignment,
                          std::vector<SDTypeMember> members );
  SDUniformComponent &register_component ( SDUniformBlock &block,
                                           const std::string &name,
                                           const SDType &type,
                                           size_t offset,
                                           size_t size );

  void create_uniform_block_components ( SDUniformBlock &block );
  size_t init_member_components ( SDUniformBlock &block,
                                  const std::string &base_name,
                                  SDTypeMember &member,
                                  size_t offset );
};
